using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using Crypto = System.Security.Cryptography;

using nms.gaming.rng.model;

namespace nms.gaming.rng
{
	public class RandomNumberGenerator : IRandomNumberGenerator
	{
		public RandomNumberGenerator()
		{
		}

        #region Generate Random Seed
        /// <summary>
        /// This method generates a random seed.
        /// The returned value will always be within the range predefined
        /// Int32 range Int32.MinValue and Int32.MaxValue
        /// </summary>
        /// </param>
        /// <returns>A random value between Int32.MinValue and Int32.MaxValue</returns>
        public Int32 GenerateRandomSeed()
        { return GenerateRandomSeed(0); }

        /// <summary>
        /// This method generates a random seed, injecting the supplied
        /// seed value into the calculation by using bitwise operations
        /// to allowing an external value to influence the random output.
        /// 
        /// The returned value will always be within the range predefined
        /// Int32 range Int32.MinValue and Int32.MaxValue
        /// </summary>
        /// <param name="seed">
        /// The seed value which will influence/adjust the output from the
        /// secure random number generator
        /// </param>
        /// <returns>A random value between Int32.MinValue and Int32.MaxValue</returns>
        public Int32 GenerateRandomSeed(Int32 seed)
        {
            /// -- reserve space for RNGCryptoServiceProvider and computed output
            byte[] baRandom = new byte[4];
            byte[] baCompute = new byte[4];

            /// -- convert Int32 into byte array
            byte[] baSeed = BitConverter.GetBytes(seed);

            /// -- generate a cryptographic byte sequence random number
            using (Crypto.RNGCryptoServiceProvider cryptoRNG = new Crypto.RNGCryptoServiceProvider())
            {
                cryptoRNG.GetNonZeroBytes(baRandom);
            }

            if (BitConverter.ToInt32(baRandom, 0) == 0)
                throw new Exception("random seed failed");

            /// -- merge the data into a new byte array
            for (Int32 iByte = 0; iByte < baRandom.Length; iByte++)
                baCompute[iByte] = (byte)((baSeed[iByte] + baRandom[iByte]) % 255);

            /// -- convert merged byte array into a Int32 value
            Int32 cryptoRandSeed = BitConverter.ToInt32(baCompute, 0);

            return cryptoRandSeed;
        }
        #endregion



        public IEnumerable<RandomNumberSet> GenerateRandomSet(int min, int max, int length, bool unique)
        {
            IEnumerable<RandomNumberSet> randomNumberSets = this.GenerateRandomSet(min, max, length, unique, 1);
            return randomNumberSets;
        }
        public IEnumerable<RandomNumberSet> GenerateRandomSet(int min, int max, int length, bool unique, int amount)
        {
            IEnumerable<RandomNumberSet> randomNumberSets;
            bool flag = !unique;
            if (flag)
            {
                randomNumberSets = this.GenerateRandomNumberSet(min, max, length, amount);
            }
            else
            {
                randomNumberSets = this.GenerateRandomSequenceSet(min, max, length, amount);
            }
            return randomNumberSets;
        }

        public IEnumerable<RandomNumber> GenerateRandom(int min, int max, int length, bool unique)
        {
            IEnumerable<RandomNumber> randomNumbers;
            bool flag = !unique;
            if (flag)
            {
                randomNumbers = this.GenerateRandomNumber(min, max, length);
            }
            else
            {
                randomNumbers = this.GenerateRandomSequence(min, max, length);
            }
            return randomNumbers;
        }


        /// <summary>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="length"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
		public IEnumerable<RandomNumberSet> GenerateRandomNumberSet(int min, int max, int length, int amount)
		{
            /// --- generate index numbers for the returning IEnumerable<RandomNumber>
            var _index = ParallelEnumerable.Range(1, amount);

            /// --- generate all the possible values as we have asked for unique values in the IEnumerable<RandomNumber>
            /// --- randomly order the values by using the OrderBy( guid => Guid.NewGuid( ) ) and limit the returned
            /// --- size of the IEnumerable<RandomNumber> to the specified length by using the Take( length )
            var _result = _index.Select(
                i => new RandomNumberSet(i)
                {
                    values = GenerateRandomNumber(min, max, length)
                               .ToArray<RandomNumber>()
                }
            );

            /// ---
            return _result.AsEnumerable<RandomNumberSet>();
		}

        /// <summary>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public IEnumerable<RandomNumber> GenerateRandomNumber(int min, int max, int length)
        {
            /// --- 
            ThreadLocal<Random> threadRandom = new ThreadLocal<Random>(() => new Random(GenerateRandomSeed(0)));

            /// --- generate index numbers for the returning IEnumerable<RandomNumber>
            var _index = ParallelEnumerable.Range(1, length);

            int _min = Math.Min(min, max);
            int _max = Math.Max(min, max);

            /// --- generate all the possible values as we have asked for unique values in the IEnumerable<RandomNumber>
            /// --- randomly order the values by using the OrderBy( guid => Guid.NewGuid( ) ) and limit the returned
            /// --- size of the IEnumerable<RandomNumber> to the specified length by using the Take( length )
            var _value = ParallelEnumerable.Repeat<Int32>(1, length)
                            .Select(i => threadRandom.Value.Next(_min, max + 1))
                            .OrderBy(guid => Guid.NewGuid())
                            .Take(length);

            /// --- zip together the _index and _value objects to create the ParallelQuery<RandomNumber>
            var _result = _index.AsParallel<Int32>().Zip(_value, (i, v) => new RandomNumber(i, v));

            return _result.AsEnumerable<RandomNumber>();
        }

        /// <summary>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="length"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public IEnumerable<RandomNumberSet> GenerateRandomSequenceSet(int min, int max, int length, int amount)
        {
            /// --- generate index numbers for the returning IEnumerable<RandomNumber>
            var _index = ParallelEnumerable.Range(1, amount);

            /// --- generate all the possible values as we have asked for unique values in the IEnumerable<RandomNumber>
            /// --- randomly order the values by using the OrderBy( guid => Guid.NewGuid( ) ) and limit the returned
            /// --- size of the IEnumerable<RandomNumber> to the specified length by using the Take( length )
            var _result = _index.Select(
                i => new RandomNumberSet(i)
                {
                    values = GenerateRandomSequence(min, max, length)
                               .ToArray<RandomNumber>()
                }
            );

            /// ---
            return _result.AsEnumerable<RandomNumberSet>();
        }

        /// <summary>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <param name="length"></param>
        /// <returns></returns>
		public IEnumerable<RandomNumber> GenerateRandomSequence(int min, int max, int length)
		{

            Int32 _min = Math.Min(min, max);
            Int32 _max = Math.Max(min, max);

            Int32 _count = _max - _min;

            /// --- generate index numbers for the returning IEnumerable<RandomNumber>
            var _index = ParallelEnumerable.Range(1,  _count + 1 );

            /// --- generate all the possible values as we have asked for unique values in the IEnumerable<RandomNumber>
            /// --- randomly order the values by using the OrderBy( guid => Guid.NewGuid( ) ) and limit the returned
            /// --- size of the IEnumerable<RandomNumber> to the specified length by using the Take( length )
            var _value = ParallelEnumerable.Range(_min, _count + 1)
                            .OrderBy(guid => Guid.NewGuid())
                            .Take(length);

            /// --- zip together the _index and _value objects to create the ParallelQuery<RandomNumber>
            var _result = _index.AsParallel<Int32>().Zip(_value, (i, v) => new RandomNumber(i, v));

            return _result.AsEnumerable<RandomNumber>();

		}




	}
}